/**
 * @date      2022/01/19
 * @auther    luke_guangzhong\@qq.com
 * @file      LinkQueue.c
 * @Software  CLion
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include "LinkQueue.h"

int random_int() {
    static unsigned int seed = 0;
    seed++;
    srand((unsigned) time(NULL) + seed * seed);
    return rand() % (1024 - 0 + 1) + 0;
}

/*******Node Functions*******/

/**
 * @brief create a share memory for a Queue Node.
 * @param[out] shmidPtr a pointer pointed to Queue Node's sharememory id. The value which is pointed is recommended to be NULL.
 * @param[in] offset the offset of sharememory key.
 * @return a QNode pointer to the share memory. NULL means a error has occurred.
 * @note shmidptr must point to a real and already initialized variable, or SIGSEGV will be triggered.
 */
static QNodePtr createShm_Node(QNodeID *shmidPtr, key_t key, int offset) {
    if (!shmidPtr) {
        return NULL;
    }

    QNodePtr shmaddr = NULL;
    key_t shmkey = (key_t) (key + offset);
    long page_size = sysconf(_SC_PAGESIZE);
    int data_size = (LINKQUEUE_NODE_SIZE + page_size - 1) & (~(page_size - 1));

    // create shared memory
    *shmidPtr = shmget(shmkey, data_size, 0666 | IPC_CREAT | IPC_EXCL);
    if (*shmidPtr == -1) {
        LinkQueueDebug("shmget failed\n");
        return NULL;
    }

    // attach shared memory
    shmaddr = shmat(*shmidPtr, NULL, 0);
    if (shmaddr == (void *) -1) {
        LinkQueueDebug("shmat failed\n");
        return NULL;
    }

    // preventive wild pointer
    shmaddr->data.data = 0;
    shmaddr->next = 0;

    // return the sharememory address
    return shmaddr;
}


/**
 * @brief Obtain the share memory address of the QNode based on share memory id.
 * @param [in] shmid the share memory id used to get address.
 * @return a QNode pointer to the share memory. NULL means a error has occurred.
 */
static QNodePtr getShm_Node(QNodeID shmid) {
    void *shmaddr = NULL;
    QNodePtr addr = NULL;

    // attach share memory
    shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void *) -1) {
        LinkQueueDebug("shmat failed");
        return NULL;
    }
    addr = shmaddr;

    // return share memory address
    return addr;
}


/**
 * @brief Detach the share memory based on the address.
 * @param [in] node the address pointed to the QNode.
 * @return -1 means execution failed. 0 indicates successful execution.
 */
static LStatus detShm_Node(QNodePtr node) {
    if (shmdt(node) == -1) {
        LinkQueueDebug("shmdt failed\n");
        return ERROR;
    }
    return OK;
}

/*******External Functions*******/

LStatus initQueue(LinkQueue *Q) {
    Q->length = 0;
    QNodeID shmid = 0;
    QNodePtr qNode = createShm_Node(&shmid, LINKQUEUE_HEAD_KEY, 0);
    LinkQueueDebug("Queue shmid=%d", shmid);

    qNode->next = 0;

    detShm_Node(qNode);

    Q->head = Q->tail = shmid;

    if (pthread_mutex_init(&(Q->mutex), NULL)) {
        LinkQueueDebug("mutex init failed!!!")
    }
    return OK;
}


LStatus destroyQueue(LinkQueue **QPtr, QueueID Qid) {
    int shmid, nextid;
    QNodePtr qNode = NULL;

    pthread_mutex_lock(&((*QPtr)->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    shmid = (*QPtr)->head;
    while (shmid != 0) {
        qNode = getShm_Node(shmid);
        if (qNode == NULL) {
            break;
        }
        nextid = qNode->next;

        detShm_Node(qNode);

        if (relShm(shmid)) {
            return ERROR;
        }
        qNode = NULL;
        shmid = nextid;
    }
    detShm_Queue(*QPtr);
    if (relShm(Qid)) {
        return ERROR;
    }
    *QPtr = NULL;

    pthread_mutex_unlock(&((*QPtr)->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return OK;
}


LStatus clearQueue(LinkQueue *Q, QueueID *Qid) {
    destroyQueue(&Q, *Qid);
    Q = createShm_Queue(Qid, LINKQUEUE_KEY);
    initQueue(Q);
    return OK;
}


bool QueueEmpty(LinkQueue *Q) {
    bool res = true;

    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    if (Q->length <= 0) {
        res = true;
    } else {
        res = false;
    }

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return res;
}


int QueueLength(LinkQueue *Q) {
    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    int temp = Q->length;

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return temp;
}


LStatus enQueue(LinkQueue *Q, QData elem) {

    int shmid = 0;
    QNodePtr newNode = createShm_Node(&shmid, LINKQUEUE_HEAD_KEY, random_int());
    if (newNode == NULL) {
        return ERROR;
    }

    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    QNodePtr tailNode = getShm_Node(Q->tail);
    LinkQueueDebug("Q.tail=%d", Q->tail);
    LinkQueueDebug("new tail=%d", shmid);
    tailNode->next = shmid;
    Q->tail = shmid;
    newNode->next = 0;

    newNode->data = elem;

    detShm_Node(tailNode);
    detShm_Node(newNode);
    Q->length++;

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return OK;
}


LStatus deQueue(LinkQueue *Q, QData *elemPtr) {
    QNodeID shmid = 0;

    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    QNodePtr frontNode = getShm_Node(Q->head);
    if (frontNode == NULL) {
        return ERROR;
    }
    shmid = frontNode->next;
    QNodePtr opNode = getShm_Node(shmid);
    if (opNode == NULL) {
        return ERROR;
    }

    frontNode->next = opNode->next;
    elemPtr->data = opNode->data.data;
    LinkQueueDebug("deleted Node value:%d", elemPtr->data);
    detShm_Node(opNode);
    relShm(shmid);
    detShm_Node(frontNode);

    Q->length--;

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return OK;
}

/*******Custom Functions*******/

LStatus rmQNode(LinkQueue *Q, QData elem) {
    QNodeID shmid = 0;
    QNodePtr preNode = getShm_Node(Q->head);

    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    shmid = preNode->next;
    QNodePtr opNode = getShm_Node(shmid);

    while (QDataCmp(opNode->data, elem)) {
        detShm_Node(preNode);
        preNode = opNode;
        shmid = opNode->next;
        opNode = getShm_Node(shmid);
        if (opNode == NULL) {
            break;
        }
    }
    if (opNode == NULL) {
        pthread_mutex_unlock(&(Q->mutex));
        LinkQueueDebug("Exit Critical Region!");
        return ERROR;
    }
    preNode->next = opNode->next;
    detShm_Node(opNode);
    relShm(shmid);
    detShm_Node(preNode);

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return OK;
}


LStatus printQueue(LinkQueue *Q) {
    QNodeID shmid = 0;

    pthread_mutex_lock(&(Q->mutex));
    LinkQueueDebug("Enter Critical Region! Function:%s, thread:%x", __FUNCTION__, pthread_self());

    if (Q->head == -1 || Q->length == 0) {
        LinkQueueDebug("This Queue doesn't exist or empty!");
        pthread_mutex_unlock(&(Q->mutex));
        LinkQueueDebug("Exit Critical Region!");
        return ERROR;
    }
    QNodePtr opNode = getShm_Node(Q->head);
    shmid = opNode->next;
    detShm_Node(opNode);

    while (shmid != 0) {
        QNodePtr opNode = getShm_Node(shmid);
        printQData(opNode->data);
        shmid = opNode->next;
        detShm_Node(opNode);
    }
    printf("end\n");

    pthread_mutex_unlock(&(Q->mutex));
    LinkQueueDebug("Exit Critical Region!");

    return OK;
}